package com.trontria.simpledatamodel;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class ModelManager {
	public static final String TAG = ModelManager.class.getSimpleName();
	private int managerState = State.STATE_NEW;
	// ========================================================================
	// Constructor
	// ========================================================================
	private static ModelManager instance;
	public static ModelManager manager() {
		if (instance == null)
			instance = new ModelManager();
		return instance;
	}
	
	private ModelManager() {
		managerState = State.STATE_NEW;
		registeredModels = new ArrayList<Model>();
	}
	public static void destroy() {
		instance = null;
	}
	// ========================================================================
	// Initialize
	// ========================================================================
	private String databaseName;
	public void config(String name) {
		databaseName = name;
		setManagerState(State.STATE_CONFIGURED);
	}
	
	/**
	 * 
	 * @param version
	 */
	public void init(Context context, int version) {
//		if (getManagerState() != State.STATE_CONFIGURED) {
//			throw new IllegalStateException("Must configure the manager before initializing it");
//		}
		
		DatabaseHelper helper = new DatabaseHelper(context, databaseName, null, version);
		database = helper.getWritableDatabase();
				
		setManagerState(State.STATE_INITIALIZED);
	}
	
	public void close() {
//		if (getManagerState() != State.STATE_INITIALIZED) {
//			throw new IllegalStateException("Database manager must be initialized before closing");
//		}
		if (database != null) {
			database.close();
			database = null;
		}
		setManagerState(State.STATE_CLOSED);
	}
	
	// ========================================================================
	// Database
	// ========================================================================
	private SQLiteDatabase database;
	private String makeTableSql(Model model) {
		StringBuilder builder = new StringBuilder("create table " + model.getModelName() + "(");
		for (int i = 0; i < model.fieldList().length; i++) {
			Field field = model.fieldList()[i];
			builder.append(field.name() + " " + dbType(field.type()));
			if (i < model.fieldList().length - 1) {
				builder.append(", ");
			}
		}
		builder.substring(0, builder.length() - 2);
		builder.append(")");
		return builder.toString();
	}
	
	public void save(Model data) {
		Log.d(TAG, "Start saving");
		ContentValues values = new ContentValues();
		for (Field field : data.fieldList()) {
			values.put(field.name(), data.getRaw(field.name()));
		}

		database.insert(data.getModelName(), null, values);
	}
	public List<?> all(Class<? extends Model> modelClass) {
		Model model = retrieveModel(modelClass);
		List<Model> dataList = new ArrayList<Model>();
		String sql = "select * from " + model.getModelName();
		Cursor cursor = database.rawQuery(sql, null);
		
		if (cursor.moveToFirst()) {
			do {
				Model data = retrieveModelData(modelClass);
				for (Field field : model.fieldList()) {
					data.putRaw(field.name(), cursor.getString(cursor.getColumnIndex(field.name())));
				}
				dataList.add(data);
			} while (cursor.moveToNext());
		}
		return dataList;
	}
	
	private String dbType(int fieldType) {
		switch (fieldType) {
		case Field.FieldType.FIELD_TYPE_INT:
			return "int";
		case Field.FieldType.FIELD_TYPE_STRING:
		default:
			return "text";
		}
	}
	// ========================================================================
	// Scheme
	// ========================================================================
	private Model retrieveModel(Class<? extends Model> modelClass) {
		Method m;
		try {
			m = modelClass.getMethod("model", (Class<?>[]) null);
			Model model = (Model) m.invoke(modelClass, (Object []) null);
			return model;
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		}
	}
	
	private Model retrieveModelData(Class<? extends Model> modelClass) {
		Method m;
		try {
			m = modelClass.getMethod("data", (Class<?>[]) null);
			Model data = (Model) m.invoke(modelClass, (Object []) null);
			return data;
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Model class must contain static methods: model() and data()");
		}
	}
	// ========================================================================
	// Model registration
	// ========================================================================
	private List<Model> registeredModels;
	public void register(Class<? extends Model> modelClass) {
//		if (getManagerState() >= State.STATE_INITIALIZED) {
//			throw new IllegalStateException("Register must be called before init, state: " + getManagerState());
//		}
		Model model = retrieveModel(modelClass);
		if (!registeredModels.contains(model))
			registeredModels.add(model);
	}
	public List<Model> getRegisteredModels() {
		return registeredModels;
	}

	public void setRegisteredModels(List<Model> registeredModels) {
		this.registeredModels = registeredModels;
	}
	// ========================================================================
	// Manager state
	// ========================================================================
	public int getManagerState() {
		return managerState;
	}

	private void setManagerState(int managerState) {
		this.managerState = managerState;
	}
	
	public static class State {
		public static final int STATE_NEW = 0x000;
		public static final int STATE_CONFIGURED = 0x001;
		public static final int STATE_INITIALIZED = 0x002;
		public static final int STATE_CLOSED = 0x003;
	}

	// ========================================================================
	// Create database
	// ========================================================================
	public class DatabaseHelper extends SQLiteOpenHelper {
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			for (Model model : registeredModels) {
				String sql = makeTableSql(model); 
				Log.d(TAG, sql);
				db.execSQL(sql);
			}
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		}
	}
}
